from typing import Callable, List, Optional, Tuple, Union

from django.db import models

from .enums import ChoiceMeta
from .utils import AttrDict

BLANK = dict(null=True, blank=True)
ChoicesType = Union[ChoiceMeta, List[Tuple]]


class ChoiceField(models.CharField):
    """
    model Fields for CharField that limit value by choices

    Note:
        1. the first arg must be verbose_name
        2. the second arg must be choices (tuple or list or Enum)

    Example:
        from toolbox import StrChoice
        class GenderChoice(StrChoice):
             none = 0, '未知'
             man = 1, '男'
             lady = 2, '女'

        class A(models.Model):
             gender = ChoiceField('性别', GenderChoice, default=GenderChoice.none)

        # class A get the same result as class B
        class B(models.Model):
             gender = models.CharField('性别', max_length=1, choices=GenderChoice, default=GenderChoice.none)  # NOQA
    """

    def get_max_choices_value_length(self, choices: ChoicesType) -> int:
        return max(len(i[0]) for i in choices)

    def __init__(self, verbose_name: Optional[str], choices: ChoicesType, **kwargs):
        if "max_length" not in kwargs:
            kwargs["max_length"] = self.get_max_choices_value_length(choices)
        super().__init__(verbose_name, choices=choices, **kwargs)


ChoicesField = ChoiceField  # Compatitable with old version


class WeakForeignKey(models.ForeignKey):
    """弱连接外键
    即只是Django orm中把它当外键，实际数据库中，并未添加外键约束
    默认db_contains=False，on_delete=models.SET_NULL, null=True, blank=True
    """

    def __init__(
        self,
        to,
        verbose_name: Optional[str] = None,
        related_name: Optional[str] = None,
        on_delete: Callable = models.SET_NULL,
        db_constraint: bool = False,
        null: bool = True,
        blank: bool = True,
        **kwargs,
    ):
        kwargs.update(
            verbose_name=verbose_name,
            db_constraint=db_constraint,
            null=null,
            blank=blank,
        )
        return super().__init__(to, on_delete, **kwargs)


class CascadeForeignKey(WeakForeignKey):
    """级联删除且默认不为空的弱连接外键
    即只是Django orm中把它当外键，实际数据库中，并未添加外键约束
    默认db_contains=False，on_delete=models.CASCADE, null=False, blank=False
    """

    def __init__(
        self,
        to,
        verbose_name: Optional[str] = None,
        related_name: Optional[str] = None,
        on_delete: Callable = models.SET_NULL,
        **kwargs,
    ):
        return super().__init__(to, verbose_name, related_name, on_delete, **kwargs)


class AttrDictMixin:
    """自动将值类型为dict的JSONField转为AttrDict，以便使用model.field.key来访问
    Example::
        from django.db.models import JSONField
        from toolbox import AttrDictMixin

        class A(models.Model, AttrDictMixin):  # AttrDictMixin须放在最右边
            foo = JSONField()

        a = A(foo={'name': 'xxx'})
        assert a.foo.name == a.foo['name'] == 'xxx'
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        _setattr = setattr
        _getattr = getattr
        _dict = dict
        _isinstance = isinstance
        _attrdict = AttrDict
        fields_iter = iter(self._meta.fields)
        for field in fields_iter:
            attname = field.attname
            v = _getattr(self, attname, False)
            if v and _isinstance(v, _dict) and not _isinstance(v, _attrdict):
                _setattr(self, attname, _attrdict(v))
